6. 表达式 和 运算符

运算符就是对数据进行运算的符号,我们小时候学数学的加减乘除就是运算符的代表。

因为运算总离不开数据,我们这里把运算符和数据类型一并讲解。

先上两个概念作为开胃菜:

  • 表达式

    • 由一个或者几个数字或者变量和运算符组合成的一行代码

    • 通常会返回一个结果

  • 运算符

    • 由一个或以上的值经过变化得到新值的过程就叫运算

    • 用于运算的符号叫运算符, 对于数字来说,加减乘除就是数字运算符

  • 运算符分类:

    • 算数运算符

    • 字符串运算符

    • 比较或者关系运算符

    • 赋值运算符

    • 逻辑运算符

    • 位运算

    • 成员运算

    • 身份运算符

6.1. 算数运算符

进行算数运算的符号成为算术运算符,算术运算符操作的数据类型是数字类型。

6.1.1. 数字类型

Python的数字类型包括:

  • 整数, 即我们数学意义上的整数,包括0, 正整数,负整数

  • 浮点数:即我们数学意淫上的小数, 带小数点,常见的写法有

    • 正常小数,即 3.1415

    • 如果整数部分是0, 则可以省略整数部分,例如 0.9 可以写成 .9

    • 科学计数法, 31415 可以写成 3.1415e4 或者 3.14315E4, 此处e可以大小写

  • 复数, 即数学意义上的复数

    • 例如: 3 + 4j, 此复数包括实部3和虚部4j

    • 如果只有虚部,也是可以的,例如 0+4j 可以写成 4j

    • 如果只有 1j 则不可以省略成 j, 只有虚部j则被认为是变量,不是复数的虚部

6.1.2. 对数字的运算符号

  • python没有自增自减运算符, 即没有Java语言中的 i++, i--等写法

  • 除常见运算符外,python的math包内有很多数学运算函数,可以使用做复杂科学计算

  • 算数运算符表

| 运算符 | 描述 | 实例 | | :—– | :————— | :———————————— | | + | 加: 两个对象相加 |a + b 输出结果 31 | | - | 减: 得到负数或是一个数减去另一个数 | a - b 输出结果 -11 | | * | 乘: 两个数相乘或是返回一个被重复若干次的字符串 | a * b 输出结果 210 | | / | 除: x 除以 y | b / a 输出结果 2.1 | | % | 取模: 返回除法的余数 | b % a 输出结果 1 | | ** | 幂: 返回x的y次幂 | a**b 为10的21次方 | | // | 取整除: 向下取接近除数的整数 | 9//2 == 4 |

  • +,- 跟正常算术运算符一模一样

a = 9+3-2
print(a)

结果:

10
  • 乘号用星号(*)代替

a = 9 * 4
print(a)

结果:

36
  • 除号用斜杠(/)代替, 此处的运算符结果一定是个小数,跟数学上的除号结果一致

# 在Python2.x 和python3.x 中,除号(/)结果不一致,此处以3系列为准
a = 9/4
print(a)

结果:

2.25
  • %: 取余运算,此处结果是数学上的整除取余方法后的余数部分

# 两个数字相除应该有商数有余数
# % 只会得到余数
a = 9 % 4
print(a)

结果:

1
  • //: 整数除法中的取商, 此处结果是数学上的整除取余方法后的余数部分

# // 表示取商运算,也叫地板除
a = 9 // 4
print(a)

结果:

2
  • **: 幂运算

# ** 表示幂运算
a = 3 ** 3
print(a)

结果:

27

6.2. 字符串运算符

字符串也可以运算,一般Python不把这个单独列出,只是作为字符串的一个知识点讲解,我们会因为是以运算符为主导讲解 数据类型,所以单独列出来。

6.2.1. 字符串

字符串运算符我们此处特指加好和称号。

  • 加号(+), 字符串相加表示两个字符串自动连接在一起成为一个新的字符串

      a = '我是刘大拿'
      b = '我爱王晓静'
    
      c = a + b
      print(c)
    
      结果:
    
      我是刘大拿我爱王晓静
    
  • 称号(*), 称号是加法的省略,此处意义基本相同,表示多个字符串重复N遍

      a =  '我爱王晓静'
    
      c = a * 3
      print(c) 
    
      结果:
    
      我爱王晓静我爱王晓静我爱王晓静
    

6.3. 比较运算符

比较运算符的结果是逻辑值,而参与比较运算的数据可以是数字,字符串等,在Python中基本万物皆可比较,但是 他们的结果只能是逻辑值。

6.3.1. 逻辑值

逻辑值也叫布尔值(bool, boolean), 表示真假,只有两个值:

  • True: 表示正向的,积极的,对的结果

  • False:表示负面的,消极的,错误的结果

注意两个的写法,只是两个单词,没有引号,第一个字母大写,其余小写

  • 两个值可以直接使用,也可以参与预算。

     # 逻辑值可以直接赋值给一个变量
     a = True
     print(a)
    
     运行结果:
         True
    
  • 逻辑值也可以参与算术运算,在算术运算中,

    • True被自动转换成数字1

    • False被自动转换成数字0

      #逻辑值作为算术运算
      #此时发生自动转换,True被当做数字1
      a = 100 + True
      print(a)
      
      #布尔值参与算术运算自动发生转换,False被当做数字0
      b = False * 2 
      print(b)
      
      运行结果:
      
          101
          0
      

同样,数字在需要逻辑值的时候也会自动转换成逻辑值,稍后会讲解

6.3.2. 比较运算符

  • 对两个变量或者值进行比较的运算符

  • 比较的结果是布尔值,即True/False

  • 以下是6个比较运算符

| 运算符 | 描述 | 实例 | | :—– | :————— | :———————————— | | == | 等于: 比较对象是否相等 | (a == b) 返回 False。 | | != | 不等于: 比较两个对象是否不相等 | (a != b) 返回 True。 | | > | 大于: 返回x是否大于y | (a > b) 返回 False。 | | < | 小于: 返回x是否小于y。所有比较运算符返回1表示真,返回0表示假。 | (a < b) 返回 True。 | | >= | 大于或等于: 返回x是否大于等于y。 | (a >= b) 返回 False。 | | <= | 小于或等于 - 返回x是否小于等于y。 | (a <= b) 返回 True。 |

注意: Python3 取消了<>作为不等于符号,使用!=作为不等于号

Achtung: 因为=被当做赋值使用,所以等于变成了==

Attention: >= 的意思是大于或者等于,即大于或者等于都返回True

# == , 等于号
a = 3 ** 4
# 下面语句执行书序是
# 1, 计算 a == 80
# 2. 把结果放入b中
b = a == 80
print(b)


# != 不等于
print( 9 != 8 )

# > 大于
# < 小于
# >= 大于等于
# <= 小于等于

运算结果:

False
True

6.4. 赋值运算符

赋值运算符通常用来对一个变量进行赋值,可以看做是把右边的值或者经过计算最后得出的值存入左边变量中.

赋值号=是把右边所有的式子都执行完后得到一个结果,然后把结果赋值给左边变量,即赋值号的运算优先级是最低的

其余的复合运算符号仅仅是相应算术运算符号的缩写

| 运算符 | 描述 | 实例 | | :—– | :————— | :———————————— | | = | 简单的赋值运算符 | c = a + b 将 a + b 的运算结果赋值为 c | | += | 加法赋值运算符 | c += a 等效于 c = c + a | | -= | 减法赋值运算符 | c -= a 等效于 c = c - a | | *= | 乘法赋值运算符 | c *= a 等效于 c = c * a | | /= | 除法赋值运算符 | c /= a 等效于 c = c / a | | %= | 取模赋值运算符 | c %= a 等效于 c = c % a | | **= | 幂赋值运算符 | c **= a 等效于 c = c ** a | | //= | 取整除赋值运算符 | c //= a 等效于 c = c // a |

# =  赋值
a = 0

# 赋值可以连续使用,以下案例表示把右边的值放入a和c中
c = a = 4

# +=, 是缩写,
a = 1
a += 7 # a = a+ 7 的缩写
print(a)


# 所有数学运算符都有缩写形式
# -=, ×=, /=, //=, %=, **=,都是缩写形式

结果:

8

6.5. 逻辑运算符

逻辑运算符是对布尔值进行计算的符号, 要求参与运算的是逻辑值,如果不是,则自动转换成逻辑值再运算。

运算符只有三个:

  • and: 逻辑与, 可以理解成我们语言中的, 即参与运算的两边都是True的时候结果是True,否则是False

  • or: 逻辑或, 可以理解成我们语言中的或者,即参与运算的两边有一个是True则结果就是True

  • not: 逻辑非, 也叫取反运算,即把后面的结果翻转,此运算符是单目运算符,即要求一个数据参与运算就可以

  • python中逻辑运算没有异或运算

  • 运算规则可以以下解释:

    • and看做乘法, or看做加法,

    • True看做1, False看做0

    • 则逻辑运算就能转换成整数数学运算

    • 最后结果如果是0则为False, 否则为True

# 逻辑运算符案例
a = True
b = False
c = True

# 以下式子等价于 d = 1 * 0 + 1
d = a and b or c
print(d)


d = a or b and a
print(d)

a = True
#对a取反,即取a的相反的值
b = not a
print(b)

运算结果为:

True
True
False

6.6. 逻辑运算的短路问题

短路问题是编程经常会遇到的一个问题,当我们进行逻辑运算的时候,我们按照运算顺序计算, 一旦能够确定整个式子未来的值,则不再进行计算,直接返回。

比如在继续逻辑运算的时候,逻辑与会看做乘号,而False会看做0, 根据数学知识我们知道, 0乘以任何值最后结果都是0, 所以,如果我们在逻辑表达水中看到是False和后面进行逻辑与运算, 电脑则不再进行后面的运算,直接判定结果为假,注意是不再对后面表达式进行计算。

通俗的讲就是电脑在结果确定的前提下合理偷懒,Python中不允许在表达式中加入赋值语句,对于允许这种操作的语言, 如果再后面的表达式中还有赋值语句,因为不会执行后面的表达式,很可能造成的结果就是我们看到明明有赋值语句但 因为没有执行,造成例如变量未定义等错误,由短路造成的此类错误很难排查。

可以参考下面案例进行理解,下面案例使用了函数等知识,如果理解困难可以跳过。

# 逻辑运算的短路案例

# 下面的逻辑表达式,a的值一定是True,则运行到or的时候,整个表达式不在向下计算
# 因为or右边是一对无意义的乱码,但程序并未执行,所以也不会报错
a = True or xxxxxxxxxxx

# 下面表达式,如果xxx中包含赋值表达式,则结果很难预期
# 代码示例(伪代码)
# 定义全局变量a
a = 100

# 定义函数,在判断中被调用
def fun_call():
    # 在函数中修改全局变量a
    # 此用法只做实验用,谨慎使用
    global a 
    a = 101
    print("Be called")
    return True
# 使用or连接会产生短路, 此时函数不会被执行,也不会产生任何后果
if 1 or fun_call():
    print(10000)
    
print(a)

短路显示结果:

10000
100
# 使用and链接不会产生短路, 会调用函数,可能会有副作用
if 1 and fun_call():
    print(10000)
    
print(a)

显示结果无短路:

Be called
10000
101

6.7. 成员运算符号

成员运算符用来检测某一个变量是否是另一个变量的成员

成员运算符号可以分为两个:

  • in: 用来判断是否包含在某个成员里面

  • not in: 可以看做是in的取反操作

# 案例, l是一个列表,里面包含五个数字,我们判断a是否也在里面
# 列表的知识会在后期介绍
l = [1,2,3,4,5]
a = 7

b = a in l
print(b)

a = 4
print(a in l)

print(a not in l)

结果如下:

False
True
False

6.8. 身份运算

用来判断两个变量是否是一个,这种判断并不是简单判断是否同一个值,而是判断是否有同一个内存区域,如果仅仅是值 相等,而内存区域不一样,身份运算也会返回否定的结果。

由于Python不同意别的语言的内存管理机制,对于的数据,Python会使用同一个内存,后面会提到,所以is用来判断 数据可能并不会返回期望的结果。

身份运算符包含两个:

  • is: 用来检测两个变量是否是同一个变量,依据是内存地址, 只有同一个内存,即同一片数据区域才会返回True

  • is not: 两个变量不是同一个变量

a = 9
b = 9
# 此处结果是True,理论上讲,a和b不应该是一个变量,此问题后面单独论述
print( a is b)

a = "I love wangxiaojing"
b = "I love wangxiaojing"
# a 和b是两个字符串,仅仅是内容相同,内存会为每一个字符串分配一个地址,因此两个
#字符串内存地址不同,所以返回结果是False
print( a is b)

运行结果:

True
False

6.8.1. is运算符和 == 的区别

  • is 是比较两个变量是否同一内存,内存地址可以用id()来获取

  • == 是比较两个变量的值是否一样

id() 可以用来检测变量的id,应该把这个id理解成是变量的内存地址

6.8.2. 小数据/小数据池

程序每定义一个变量会生成一块新的内存,然后把值放入相应内存中,这也导致了即使声明两个内容一样的变量, 但用is来测试两个变量也可能不一样的原因。
示例如下面代码所示:

a = 123456
b = 123456

# id()用来得到变量的内存地址
print(id(a))
print(id(b))

print(a is b ) # 判断id是否一致
print(a == b) # 判断值是否一致

运行结果:

139659931486192
139659931485808
False
True

Python为了提高效率,会采用一个数据池,用来存放常用的且体积不大的数据,这样每次声明对应值的变量的时候,就不需要开辟内存和拷贝内容,而是直接指向对应的已经存在的地址,这样会导致如果定义两个变量,变量内容一致,则is返回结果为真的情况。

小数据池包含的内容分为三类:

  • 数字

  • 布尔值

  • 字符串

对小数据的规定如果是数字和布尔值比较简单,但对字符串比较繁琐,我们一一讲述:

  • 数字: [-5,256]之间的整数为小数据,在内存常驻

    # 小数据定义的不同变量,如果值一样,则is返回真
    a = 67
    b = 67
    print(a is b)
    
    # 267不属于小数据
    a = 267
    b = 267
    print(a is b)
    

    输出结果:

      True
      False
    
  • 布尔值: 布尔值一共就两个,常驻小数据池,妥妥滴~~~~

    # 逻辑值常驻内存
    a = True
    b = True
    
    print(a is b)
    

    运行结果是:

      True
    
  • 字符串: 这个比较繁琐了,它的规则包含以下几个方面:

    • 字符串的长度为0或者1,默认都采用了驻留机制(小数据池)

      # 字符串长度为1或者0的都入住小数据池
      a = ""
      b = ""
      # a,b的长度为0,所以是一个
      print(a is b)
      
      a = '#'
      b = '#'
      # a,b的长度为1, 所以是一个
      print(a is b)
      

      运行结果是:

       True
       True
      
    • 字符串的长度>1,且只含有大小写字母,数字,下划线时,才会默认驻留

      # 小数据池存的字符串要求只包含三类内容
      a = "9898ABCabc_"
      b = "9898ABCabc_"
      print(a is b)
      
      # 以下字符串内容超出小数据池存放范畴, 包含空格和中文
      a = "I love 王晓静"
      b = "I love 王晓静"
      print(a is b)
      

      运行结果是:

         True
         False
      
    • 乘法得到的字符串,如果乘数为1,则驻留内存池,参加以下案例

       a = "#$$%akiekk dkki lsldafaewfa asdf asfd afa  aef  "
       b = a*1
       c = "#$$%akiekk dkki lsldafaewfa asdf asfd afa  aef  "
      
       print(a is b)
       print(a is c)
      

      运行结果是:

         True
         False
      

    关于乘数为1的结果,很多材料又进行了细分,但结果都是一样的,没查到细分的原因,这里统一概括成只要乘数为1就是默认驻留内存 >

    • 乘法得到的字符串,乘数大于1

      • 如果字符串只包含数字,大小写字母,下划线,则如果总长度不大于20,驻留内存

      • 其余的单独开辟内存空间

      实验采用Python3.8, 得到的结果跟上述规则相反,没找到更详细资料,详见代码: > aa = “aaa”

         bb = aa * 4
         cc = aa * 4
         print(id(bb))
         print(id(cc))
         print(bb is cc)
      

      运行结果如下,跟规则相反:

         140685124194096
         140685124295216
         False
      

6.9. Python位运算符

对于非计算机专业来讲,此内容除非明确要用,暂时不推荐学习

按位运算符是把数字看作二进制来进行计算, 按位逐个位置进行计算。

就是在二进制基础上进行的逻辑运算,将0视为False,将1 视为True进行的运算。

bin()函数可以把数字转为二进制

按位运算如下:

  • 按位与运算(&):按位与运算就是将数据转化为2进制数据然后进行每个位上的逻辑与运算

      变量 = 值1  &  值2
    
  • 按位或运算(|): 按位或运算就是将数据转化为2进制数据然后进行每个位上的逻辑或运算

    变量 = 值1 | 值2
    
  • 按位非运算(~): 按位非运算就是将数据转化为2进制数据然后进行每个位上的逻辑非运算

    变量 = ~
    

    注意:按位非运算之后计算机会进行反码和补码的相关操作之后进行数据存储。

  • 按位抑或运算(^):按位抑或运算就是将数据转化为2进制数据然后进行每个位上的逻辑抑或运算

    变量 = 值1 ^ 值2
    
  • 左移运算(<<): 将数据转化为二进制之后,将二进制数据的所有数据向左移动指定的位数

    变量 =  << 移动位数
    

    特征:左移N位相当于乘以2的N次方,此方法速度远高于乘法

  • 右移运算(>>): 将数据转化为二进制之后,将二进制数据的所有数据向右移动指定的位数,如果数据移除右侧边界,则会被删除,实际上相当于采用了地板除!

      变量 =  >> 移动位数
    

    特征:右移N位相当于除以2的N次方,舍弃小数部分。此方法速度远高于除

| 运算符 | 描述 | 实例 | | :—– | :———————————————————– | :———————————————————– | | & | 按位与运算符:参与运算的两个值,如果两个相应位都为1,则该位的结果为1,否则为0 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 | | | | 按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 | | ^ | 按位异或运算符:当两对应的二进位相异时,结果为1 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 | | ~ | 按位取反运算符:对数据的每个二进制位取反,即把1变为0,把0变为1。~x 类似于 -x-1 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 | | << | 左移动运算符:运算数的各二进位全部左移若干位,由”<<”右边的数指定移动的位数,高位丢弃,低位补0。 | a << 2 输出结果 240 ,二进制解释: 1111 0000 | | >> | 右移动运算符:把”>>”左边的运算数的各二进位全部右移若干位,”>>”右边的数指定移动的位数 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |

a = 0b1001100
b = 0b111

print("Bin of 76:", a)
print("Bin of 7:", b)

输出结果,自动转换为十进制:

Bin of 76: 76
Bin of 7: 7
# 结果能显示出如果位数不足,则右对其后左边补0
c = a & b
print("a & b = ", c)

输出结果:

a & b =  4
# 按位与
c = a | b
print('a | b = ', c)

输出结果:

a | b =  79
# 按位反,即原来是1的变为0,原来是0的变为1
c = ~a
print("a =", bin(a))
print('~a = ', bin(c))

输出结果:

a = 0b1001100
~a =  -0b1001101

简单解释下上面原因,在计算机里,a按位反会的到一个负数,负数在计算机中需要用补码表示~~~~

#右移N位相当于除以2的N次方,舍弃小数部分。此方法速度远高于除法

a = 0b10011
print("a = ", a)
print("a>>3 = ", a>>3)

#对负数进行移动
#负数的右移动相当于 (x-1)/2
b = -0b1100
print("b = ", b)
# 负数的右移:需要保持数为负数,所以操作是对负数的二进制位左边补1。
# 如果一直右移,最终会变成-1,即(-1)>>1是-1

# 此数操作是b的补码右移,高位补1,然后变原码
print("b>>2=", bin(b>>2))
print("b>>2=", b>>2)

输出结果:

a =  19
a>>3 =  2
b =  -12
b>>2= -0b11
b>>2= -3
# 将数据转化为二进制之后,将二进制数据的所有数据向左移动指定的位数

# 左移相当于乘以2
a = 13
print("a=", a)
print("a<<2=", a<<2)

输出结果:

a= 13
a<<2= 52
# 对于负数左移同样道理
a = -13
print("a=", a)
print("a<<2=", a<<2)

输出结果:

a= -13
a<<2= -52

6.9.1. 原码/反码/补码

数字在计算机内部使用二进制来表示,考虑到运算的方便性,正数负数会采用不同的码制表示:

  • 正数的原、反、补码都是它本身

  • 负数的原码最高位为1开头,反码是最高符号位不变,其余位在原码的基础上取反

  • 补码是在反码的基础上+1即可得到.

6.10. 运算符的优先级问题

  • 永远记住,括号具有最高优先级, 赋值符具有最低优先级

  • 优先级表格

| 运算符 | 描述 | | :———————– | :—————————————————– | | ** | 指数 (最高优先级) | | ~ + - | 按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@) | | * / % // | 乘,除,取模和取整除 | | + - | 加法减法 | | >> << | 右移,左移运算符 | | & | 位 ‘AND’ | | ^ | | 位运算符 | | <= < > >= | 比较运算符 | | == != | 等于运算符 | | = %= /= //= -= += *= **= | 赋值运算符 | | is is not | 身份运算符 | | in not in | 成员运算符 | | not and or | 逻辑运算符 |